#
# Knowledge Base module
#
import collections
import logging
import types

log = logging.getLogger(__name__)

class _KnowledgeBase(object):
    """Main KnowledgeBase"""
    
    def __init__(self):
        log.debug('Constructed KnowledgeBase instance')
        self.kb = []
        self.id_generators = {}
        self.lookup_tables = {}


    def dump(self):
        return list(self.kb)


    def add(self, root, *items):
        """Add specified items to knowledge base.  A string representation
        is stored in the main knowledgebase, and associated attributes are
        stored in associated lookup tables.
        
        The first item is expected to be the root item which any others
        are associated with.
        
        Returns a list of Item instances
        """
        # See if we need to translate or lookup root item
        facts = [self._translate_root(root)]
        facts.extend(items)
        
        # Setup id numbers for new items attributes
        [self._generate_id(item) for item in facts]
        
        # Store associated lookup values
        map(self._store_lookup_value, items)
        
        # Add full representation to main knowledge table
        self.kb.append('~'.join([str(item) for item in facts]))
        return facts


    def _translate_root(self, raw_root):
        """Translates root element of new knowledge sequence from string
        representation into Item form"""
        item = raw_root
        if isinstance(raw_root, str):
            type_name,id = raw_root.split(':')
            item = Item(type_name)
            item.id = int(id)
        return item


    def _generate_id(self, item):
        """Generator function for generating ids.  Skips Items with ids
        already assigned"""
        if item.id is Item.NO_ID:
            idgen = self.id_generators.get(item.type_name, _id_generator())
            item.id = idgen.next()
            self.id_generators[item.type_name] = idgen


    def _store_lookup_value(self, item):
        """Stores a lookup value"""
        if item.value is not None:
            # Find lookup table or blank
            lookuptable = self.lookup_tables.get(item.type_name, {})
            # See if we have a match already
            found = False
            for lookupid in lookuptable:
                lookup = lookuptable[lookupid]
                if lookup['value'] == item.value:
                    item.id = lookupid
                    lookup['frequency'] = lookup['frequency'] + 1
                    found = True
                    break
            if not found:
                match = {'value':item.value, 'frequency':1}
                lookuptable[item.id] = match
            # Store value back into lookup
            self.lookup_tables[item.type_name] = lookuptable
        else:
            #log.debug("Skipping lookup store for %s", item)
            pass


    def lookup(self, query):
        """Returns translated lookup for specified query.  Query should be of
        the form typname:id
        Throws KeyError if value could not be found"""
        #log.debug('Looking up for query: %s', query)
        table, str_id = query.split(':')
        return self.lookup_tables[table][int(str_id)]['value']


    def _find(self, query):
        """Search knowledge base for entries matching string query"""
        log.debug('Finding %s', query)
        results = [line for line in self.kb if line.find(query) > -1]
        return results
        
    def _findstart(self, query):
        """Search knowledge base for entries startingwith string query"""
        log.debug('Finding %s', query)
        results = [line for line in self.kb if line.startswith(query)]
        return results

    def sentence_contains(self, sentence, item):
        """Utility method to determine if a knowledge sentence contains a
        specific item or not"""
        # TODO: Remove I think
        parts = sentence.split('~')
        for p in parts:
            if p.split(':')[0] == item:
                return True
        return False


    def get(self, object_str):
        """Returns reconstituted object of specified object
            Expects a single root typename, e.g. player or team
        """
        log.debug('Getting %s', object_str)
        matches = [line for line in self.kb if line.startswith(object_str+'~')]
        root = {}
        for match in matches:
            data = root
            parts = match.split('~')
            part_len = len(parts[1:])
            for idx,part in enumerate(parts[1:]):
                try:
                    value = self.lookup(part)
                    key = part.split(':')[0]
                    if key in data:
                        tmp = None
                        if isinstance(data[key], types.ListType):
                            tmp = data[key]
                        else:
                            tmp = [data[key]]
                        tmp.append(value)
                        data[key] = tmp
                    else:
                        # we are adding a new value
                        # and might need an intermediate dictionary
                        if idx < (part_len-1):
                            # need intermediate
                            if value not in data:
                                data[value] = {}
                            data = data[value]
                        else:
                            data[key] = value
                except KeyError as err:
                    # No lookup table for value
                    log.debug("No lookup table found for value, probably a relationship")
        return root
    
    def load_state(self, data):
        """Loads internal state from a previously pickled dictionary"""
        self.kb = data['kb']
        self.lookup_tables = data['lt']
        for gen in data['gen']:
            self.id_generators[gen] = _id_generator(data['gen'][gen])
            
    def dump_state(self):
        """Generates a dictionary of internal state suitable for pickling"""
        todump = {'kb':self.kb, 'lt':self.lookup_tables}
        generator_states = {}
        for gen in self.id_generators:
            generator_states[gen] = self.id_generators[gen].next() - 1
        todump['gen'] = generator_states
        return todump

def _id_generator(start=0):
    """Generator function for monotonically increasing integers"""
    current = start
    while True:
        yield current
        current += 1


class Item(object):
    """Root level instance which is stored in the knowledgebases.  Attributes
    are associated with specific Items
    """
    NO_ID = -1
    def __init__(self, type_name, value=None):
        self.id = Item.NO_ID
        self.type_name = type_name
        self.value = value


    def __str__(self):
        return "%s:%d" % (self.type_name, self.id)


# Singletons
KnowledgeBase = _KnowledgeBase()
